#include "ZIniParser.h"


const ZIniParser::KeyValueMap ZIniParser::emptyKeyValueMap;

/**
\brief Serialize to the INI file format.
*/
std::string ZIniParser::Serialize() const
{
	std::string output;
	for (SectionMap::const_iterator section = mSections.begin(); section != mSections.end();++section)
	{
		if (output.length())
			appendLine(output, "");
		appendLine(output, makeSectionText(section->first));
		for (KeyValueMap::const_iterator keyvalue = section->second.begin(); keyvalue != section->second.end();++keyvalue)
		if (keyvalue->first.length())
			appendLine(output, makeKeyValueText(keyvalue->first, keyvalue->second));
	}
	return output;
}

/**
\brief Deserialize from the INI file format.
\param data The INI data.
\param [out] errorLine The error line (only has a meaning when this function failed).
\return true if it succeeds, false if it fails.
*/
bool ZIniParser::Deserialize(const std::string & data, int & errorLine)
{
	//initialize
	errorLine = 0;
	Clear();

	//read lines
	std::vector<std::string> lines;
	std::string curLine;

	for (size_t i = 0; i < data.size();++i)
	{
		switch (data[i])
		{
		case '\r':
			break;
		case '\n':
			lines.push_back(trim(curLine));
			curLine.clear();
			break;
		default:
			curLine += data[i];
		}
	}
	if (curLine.length())
		lines.push_back(trim(curLine));

	//parse lines
	std::string section = "";
	for (size_t i = 0; i < lines.size(); ++i)
	{
		const std::string& line = lines[i];
		errorLine++;
		switch (getLineType(line))
		{
		case Invalid:
			return false;

		case Comment:
		case Empty:
			continue;

		case KeyValue:
		{
			std::string key;
			std::string value;
			if (!section.length() ||
				!parseKeyValueLine(line, key, value) ||
				!SetValue(section, key, value))
				return false;
		}
			break;

		case Section:
		{
			if (!parseSectionLine(line, section))
				return false;
		}
			break;

		default:
			return false;
		}
	}
	return true;
}

/**
\brief Sets a value. This will overwrite older values.
\param section The section. Must have a length greater than one.
\param key The key. Must have a length greater than one.
\param value The value. Can be empty (effectively removing the value).
\return true if the value was set successfully, false otherwise.
*/
bool ZIniParser::SetValue(const std::string & section, const std::string & key, const std::string & value)
{
	std::string trimmedSection = trim(section);
	std::string trimmedKey = trim(key);
	if (!trimmedSection.length() || !trimmedKey.length())
		return false;
	SectionMap::iterator found = mSections.find(trimmedSection);
	if (found != mSections.end())
		found->second[trimmedKey] = value;
	else
	{
		KeyValueMap keyValueMap;
		keyValueMap[trimmedKey] = value;
		mSections[trimmedSection] = keyValueMap;
	}
	return true;
}

/**
\brief Removes all key/value pairs from a section.
\param section The section to clear.
\return true if it succeeds, false otherwise.
*/
bool ZIniParser::ClearSection(const std::string & section)
{
	std::string trimmedSection = trim(section);
	if (!trimmedSection.length())
		return false;
	SectionMap::iterator found = mSections.find(trimmedSection);
	if (found == mSections.end())
		return false;
	mSections.erase(found);
	return true;
}


/**
\brief Gets a value.
\param section The section.
\param key The key.
\return The value. Empty string when the value was not found or empty.
*/
std::string ZIniParser::GetValue(const std::string & section, const std::string & key) const
{
	std::string trimmedSection = trim(section);
	std::string trimmedKey = trim(key);
	if (!trimmedSection.length() || !trimmedKey.length())
		return "";
	SectionMap::const_iterator sectionFound = mSections.find(trimmedSection);
	if (sectionFound == mSections.end())
		return "";
	const KeyValueMap & keyValueMap = sectionFound->second;
	KeyValueMap::const_iterator keyFound = keyValueMap.find(trimmedKey);
	if (keyFound == keyValueMap.end())
		return "";
	return keyFound->second;
}


const ZIniParser::KeyValueMap& ZIniParser::GetValues(const std::string & section)const
{
	std::string trimmedSection = trim(section);
	if (!trimmedSection.length())
		return emptyKeyValueMap;
	SectionMap::const_iterator sectionFound = mSections.find(trimmedSection);
	if (sectionFound == mSections.end())
		return emptyKeyValueMap;
	else
		return sectionFound->second;
}


/**
\brief Gets the section names.
\return List of section names.
*/
std::vector<std::string> ZIniParser::Sections() const
{
	std::vector<std::string> sections;
	sections.reserve(mSections.size());
	for (SectionMap::const_iterator section = mSections.begin(); section != mSections.end();++section)
		sections.push_back(section->first);
	return sections;
}

/**
\brief Gets keys in a given section.
\param section The section.
\return List of keys in the section. Empty if the section is not found or empty.
*/
std::vector<std::string> ZIniParser::Keys(const std::string & section) const
{
	std::vector<std::string> keys;
	std::string trimmedSection = trim(section);
	if (trimmedSection.length())
	{
		SectionMap::const_iterator found = mSections.find(trimmedSection);
		if (found != mSections.end())
		{
			keys.reserve(found->second.size());
			for (KeyValueMap::const_iterator key = found->second.begin(); key != found->second.end();++key)
				keys.push_back(key->first);
		}
	}
	return keys;
}


ZIniParser::LineType ZIniParser::getLineType(const std::string & line)
{
	size_t len = line.length();
	if (!len)
		return Empty;
	if (line[0] == '[' && line[len - 1] == ']')
		return Section;
	if (line[0] == ';')
		return Comment;
	if (line.find('=') != std::string::npos)
		return KeyValue;
	return Invalid;
}

std::string ZIniParser::trim(const std::string & str)
{
	size_t len = str.length();
	if (!len)
		return "";
	size_t pre = 0;
	while (str[pre] == ' ')
		pre++;
	size_t post = 0;
	while (str[len - post - 1] == ' ' && post < len)
		post++;
	size_t sublen = len - post - pre;
	return sublen > 0 ? str.substr(pre, len - post - pre) : "";
}

bool ZIniParser::parseKeyValueLine(const std::string & line, std::string & key, std::string & value)
{
	size_t pos = line.find('=');
	key = trim(line.substr(0, pos));
	value = trim(line.substr(pos + 1));
	size_t len = value.length();
	if (len && value[0] == '\"' && value[len - 1] == '\"')
		value = unescapeValue(value);
	return true;
}

std::string ZIniParser::escapeValue(const std::string & value)
{
	if (!needsEscaping(value))
		return value;
	std::string escaped = "\"";

	for (size_t i = 0; i < value.size(); ++i)
	{
		switch (value[i])
		{
		case '\"':
			escaped += "\\\"";
			break;
		case '\\':
			escaped += "\\\\";
			break;
		case '\r':
			escaped += "\\r";
			break;
		case '\n':
			escaped += "\\n";
			break;
		case '\t':
			escaped += "\\t";
			break;
		default:
			escaped += value[i];
		}
	}
	escaped += "\"";
	return escaped;
}

std::string ZIniParser::unescapeValue(const std::string & str)
{
	std::string result;
	bool bEscaped = false;
	for (size_t i = 0; i < str.size(); ++i)
	{
		if (!bEscaped)
		{
			switch (str[i])
			{
			case '\"':
				break;
			case '\\':
				bEscaped = true;
				break;
			default:
				result += str[i];
			}
		}
		else
		{
			switch (str[i])
			{
			case 'r':
				result += '\r';
				break;
			case 'n':
				result += '\n';
				break;
			case 't':
				result += '\t';
				break;
			default:
				result += str[i];
			}
			bEscaped = false;
		}
	}
	if (bEscaped)
		result += '\\';
	return result;
}


































